/*
Copyright 2010 Dmitry Barashev

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package net.barashev.seclub.sample;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

import net.barashev.seclub.CmdLineOptions;
import net.barashev.seclub.corpus.Corpus;
import net.barashev.seclub.corpus.Document;
import net.barashev.seclub.corpus.MemoryCorpus;
import net.barashev.seclub.io.BufferManager;
import net.barashev.seclub.io.DataMarshaller;
import net.barashev.seclub.io.IteratingSlot;
import net.barashev.seclub.io.OutputSlot;
import net.barashev.seclub.io.Sorter;
import net.barashev.seclub.io.file.DiskFileManager;
import net.barashev.seclub.io.file.FileManager;
import net.barashev.seclub.io.file.MemoryFileManager;
import net.barashev.seclub.io.file.VFile;
import net.barashev.seclub.io.marshaller.IntegerComparator;
import net.barashev.seclub.io.marshaller.IntegerMarshaller;

/**
 * Dummy search engine with just 3 documents capable of doing boolean search queries.
 * @author Dmitry Barashev
 *
 */
public class DummySearchEngine {
    private Corpus corpus;
    private BufferManager bufferMgr;
    private FileManager fileMgr;

    public DummySearchEngine(Corpus corpus, BufferManager bufferMgr, FileManager fileMgr) {
        this.corpus = corpus;
        this.bufferMgr = bufferMgr;
        this.fileMgr = fileMgr;
    }

    public void index() throws IOException {
        Set<String> tokens = new HashSet<String>();
        for (Document doc: this.corpus.getDocuments()) {
            for (StringTokenizer tkzr = new StringTokenizer(doc.getContent()); tkzr.hasMoreElements();) {
                String token = tkzr.nextToken();
                tokens.add(token);
                String unsortedPostingListFilename = "_" + token;
                VFile<Integer> file = this.fileMgr.getFile(unsortedPostingListFilename);
                if (file == null) {
                    file = this.fileMgr.createFile(unsortedPostingListFilename, new IntegerMarshaller());
                }
                OutputSlot<Integer, DataMarshaller<Integer>> slot = this.bufferMgr.allocateOutputSlot(file, true);
                slot.append(doc.getID());
                slot.flush();
                slot.release();
            }
        }
        for (String token : tokens) {
            VFile<Integer> unsortedPL = this.fileMgr.getFile("_" + token);
            assert unsortedPL != null;
            VFile<Integer> sortedPL = this.fileMgr.createFile(token, new IntegerMarshaller());
            Sorter.multiwayMergeSort(unsortedPL, sortedPL, new IntegerComparator(), this.bufferMgr, this.fileMgr, this.bufferMgr.getBufferCount() - 1);
        }
    }

    public List<Integer> search(String query) throws IOException {
        List<VFile<Integer>> pls = new ArrayList<VFile<Integer>>();
        for (StringTokenizer tkzr = new StringTokenizer(query); tkzr.hasMoreElements();) {
            VFile<Integer> pl = this.fileMgr.getFile(tkzr.nextToken());
            if (pl == null) {
                return null;
            } else {
                pls.add(pl);
            }
        }
        if (pls.isEmpty()) {
            return null;
        }
        List<Integer> result = new ArrayList<Integer>();
        HashMap<IteratingSlot<Integer,DataMarshaller<Integer>>,Integer> heads = Sorter.initialPullHeads(this.bufferMgr, pls);
        if (intersectPostingLists(heads, result)) {
            return result;
        }
        return null;
    }

    private boolean intersectPostingLists(
            HashMap<IteratingSlot<Integer, DataMarshaller<Integer>>, Integer> heads, List<Integer> result) throws IOException {
        int size = heads.size();
        while(size == heads.size()) {
            Integer sameDocid = getSameValueFromTops(heads);
            if (sameDocid != null) {
                result.add(sameDocid);
                pullAll(heads);
            } else {
                Sorter.findMinAndPull(new IntegerComparator(), heads);
            }
        }
        return true;
    }

    private void pullAll(
            HashMap<IteratingSlot<Integer, DataMarshaller<Integer>>, Integer> heads) throws IOException {
        for (Iterator<Map.Entry<IteratingSlot<Integer,DataMarshaller<Integer>>, Integer>> it =
            heads.entrySet().iterator(); it.hasNext();) {
            Map.Entry<IteratingSlot<Integer,DataMarshaller<Integer>>, Integer> entry = it.next();
            IteratingSlot<Integer, DataMarshaller<Integer>> slot = entry.getKey();
            Integer idxRecord = entry.getValue();
            if (idxRecord == slot.getRecordCount()) {
                if (slot.pull()) {
                    idxRecord = 0;
                    entry.setValue(0);
                }
                else {
                    it.remove();
                    continue;
                }
            }
            else {
                entry.setValue(idxRecord+1);
            }
        }
    }

    private Integer getSameValueFromTops(HashMap<IteratingSlot<Integer, DataMarshaller<Integer>>, Integer> heads) throws IOException {
        Integer resultDocid = null;
        for (Iterator<Map.Entry<IteratingSlot<Integer,DataMarshaller<Integer>>, Integer>> it =
            heads.entrySet().iterator(); it.hasNext();) {
            Map.Entry<IteratingSlot<Integer,DataMarshaller<Integer>>, Integer> entry = it.next();
            IteratingSlot<Integer, DataMarshaller<Integer>> slot = entry.getKey();
            Integer idxRecord = entry.getValue();
            if (idxRecord == slot.getRecordCount()) {
                if (slot.pull()) {
                    idxRecord = 0;
                    entry.setValue(0);
                }
                else {
                    it.remove();
                    resultDocid = null;
                    break;
                }
            }
            Integer curDocid = slot.getBlock(0).get(idxRecord);
            if ((resultDocid != null) &&  !resultDocid.equals(curDocid)) {
                return null;
            }
            resultDocid = curDocid;
        }
        return resultDocid;

    }
    public static void main(String[] args) throws IOException, CmdLineException {
        CmdLineOptions options = new CmdLineOptions();
        CmdLineParser parser = new CmdLineParser(options);
        parser.parseArgument(args);

        MemoryCorpus corpus = new MemoryCorpus();
        corpus.addDocument(2, "bar baz");
        corpus.addDocument(1, "foo bar");
        corpus.addDocument(3, "bar zab waz");

        DiskFileManager fileMgr = new DiskFileManager(options.index_dir);
        System.out.println("Creating index in " + fileMgr.getRoot().getAbsolutePath());
        BufferManager bufferMgr = new BufferManager(options.block_size_bytes, options.buffer_size_blocks);
        DummySearchEngine searchEngine = new DummySearchEngine(corpus, bufferMgr, fileMgr);
        searchEngine.index();

        BufferedReader inputReader = new BufferedReader(new InputStreamReader(System.in));
        String input;
        System.out.println("Type your query separating keywords with spaces, like this:\n\nfoo bar\n\n Enter empty string to exit.");
        do {
            input = inputReader.readLine();
            List<Integer> result = searchEngine.search(input);
            if (result != null) {
                System.out.println("Matching docids: " + result);
            } else {
                System.out.println("No matches");
            }
        } while(!"".equals(input));
    }
}
